import asyncio
import concurrent
import socket
import threading

import aiohttp
import platform
import subprocess
import sys
import time
from concurrent.futures import ThreadPoolExecutor
from functools import lru_cache
import requests
import ujson as json
import pybase64 as base64
from aiohttp import ClientTimeout

from cls import LocalFile, SubConvert, StrText, IpAddress

# 配置

SUB_URL_ARRAY1 = [

    "https://raw.githubusercontent.com/mfuu/v2ray/master/v2ray",
    "https://raw.githubusercontent.com/peasoft/NoMoreWalls/master/list_raw.txt",
    "https://raw.githubusercontent.com/ermaozi/get_subscribe/main/subscribe/v2ray.txt",
    "https://raw.githubusercontent.com/aiboboxx/v2rayfree/main/v2",
    "https://raw.githubusercontent.com/mahdibland/V2RayAggregator/master/sub/splitted/vmess.txt",
    "https://raw.githubusercontent.com/mahdibland/V2RayAggregator/master/sub/splitted/trojan.txt",
    "https://raw.githubusercontent.com/freefq/free/master/v2",
    "https://raw.githubusercontent.com/Pawdroid/Free-servers/main/sub",
    "https://raw.githubusercontent.com/w1770946466/Auto_proxy/main/Long_term_subscription1",
    "https://raw.githubusercontent.com/w1770946466/Auto_proxy/main/Long_term_subscription2",
    "https://raw.githubusercontent.com/w1770946466/Auto_proxy/main/Long_term_subscription3",
    "https://raw.githubusercontent.com/w1770946466/Auto_proxy/main/Long_term_subscription4",
    "https://raw.githubusercontent.com/w1770946466/Auto_proxy/main/Long_term_subscription5",
    "https://raw.githubusercontent.com/w1770946466/Auto_proxy/main/Long_term_subscription6",
    "https://raw.githubusercontent.com/w1770946466/Auto_proxy/main/Long_term_subscription7",
    "https://raw.githubusercontent.com/w1770946466/Auto_proxy/main/Long_term_subscription8",
    'https://raw.githubusercontent.com/MrPooyaX/VpnsFucking/main/Shenzo.txt',
    'https://raw.githubusercontent.com/MrPooyaX/SansorchiFucker/main/data.txt',

]

SUB_URL_ARRAY2 = [

    'https://raw.githubusercontent.com/freefq/free/master/v2',
    'https://raw.githubusercontent.com/soroushmirzaei/telegram-configs-collector/main/protocols/vless',
    'https://raw.githubusercontent.com/ts-sf/fly/main/v2',
    'https://raw.githubusercontent.com/aiboboxx/v2rayfree/main/v2',
    'https://raw.githubusercontent.com/mahdibland/ShadowsocksAggregator/master/Eternity.txt',
    'https://raw.githubusercontent.com/yebekhe/TelegramV2rayCollector/main/sub/normal/mix',
    'https://raw.githubusercontent.com/sarinaesmailzadeh/V2Hub/main/merged',
    'https://raw.githubusercontent.com/freev2rayconfig/V2RAY_SUBSCRIPTION_LINK/main/v2rayconfigs.txt',
    'https://raw.githubusercontent.com/soroushmirzaei/telegram-configs-collector/main/protocols/shadowsocks',
    'https://raw.githubusercontent.com/ALIILAPRO/v2rayNG-Config/main/sub.txt',
    'https://raw.githubusercontent.com/mfuu/v2ray/master/v2ray',
    'https://raw.githubusercontent.com/resasanian/Mirza/main/sub',
    'https://raw.githubusercontent.com/soroushmirzaei/telegram-configs-collector/main/protocols/reality',

]

# 全局变量
unique_node_names = set()
nodecount = 0
datecont = time.strftime('%m-%d', time.localtime(time.time()))
v2ry_nodes = ''
clashurl = ''
openclashurl = ''
clash_node_url = ''
proxies_url = ''
clashname = ''
telename = ''

# 创建线程本地存储
thread_local = threading.local()


def get_thread_local_data():
    # 如果线程本地存储中没有 'data'，则初始化它
    if not hasattr(thread_local, 'data'):
        thread_local.data = {}
    return thread_local.data


async def fetch_url(session, url):
    try:
        async with session.get(url, timeout=ClientTimeout(total=250, connect=60)) as response:
            if response.status != 200:
                print(f"[GET Code {response.status}] Download sub error on link: {url}")
                return None
            content = await response.read()
            try:
                print(f"Get node link on sub : {url} ")
                return base64.b64decode(content, encodings="utf-8").decode("utf-8")
            except:
                print('未使用base64加密，直接添加')
                return content.decode('utf-8')
    except Exception as e:
        print(f"[Unknown Error] Download sub error on link: {url}")
        print(e)
        return None


async def select_sub_urls(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        return [result for result in await asyncio.gather(*tasks) if result]


@lru_cache(maxsize=1000)
def get_country(ip):
    return IpAddress.get_country(ip)


def ping_test(host, count=4):
    if platform.system() == "Windows":
        cmd = ['ping', '-n', str(count), host]
    else:
        cmd = ['ping', '-c', str(count), host]
    data = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    return data.returncode == 0


def port_open(ip, port):

    port = int(port)
    print('\033[1m*Port\033[0m %s:%d' % (ip, port)),
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(1)
        s.connect((ip, port))
        s.shutdown(1)
        print(f'{ip}:{port}\033[1;32m.... is OK.\033[0m')
        return True
    except Exception as e:
        print(e)
        print(f'{ip}:{port}\033[1;31m.... is down!!!\033[0m')
        return False

def node_handler(j):
    global nodecount, clashurl, openclashurl, clash_node_url, proxies_url, clashname, telename, v2ry_nodes

    # 获取线程本地数据
    local_data = get_thread_local_data()

    # 在本地数据中进行操作，不会影响其他线程
    local_data['processed_node'] = j
    try:
        nodestr = j + ''
        if nodecount >= nodes_num:
            print(f'\n[保留{nodecount}条节点，忽略多余节点]: {j}')
            return

        onenode = ''
        cipher = ''
        server = ''
        port = ''

        if j.startswith("vmess://"):
            j = SubConvert.check_url_v2ray_vmess(j)
            j = base64.b64decode(j[8:]).decode('utf-8')
            node = json.loads(j)
            server = node['add']
            port = node['port']
            newname = node['ps']
            cipher = node['scy']
            onenode = SubConvert.v2ray_vmess_to_clash(j)
        elif j.startswith("ss://"):
            onenode = SubConvert.url_ss_to_json(j)
            if onenode:
                node = json.loads(onenode)
                server = node['server']
                port = node['port']
                cipher = node['cipher']
                password = node['password'].replace('<', '').replace('>', '').replace('!', '')
                newname = f'[{datecont}]-{get_country(server)}-{str(nodecount).zfill(3)}-{server}'
                onenode = f'  - {{name: \'{newname}\', cipher: {cipher}, password: {password}, server: {server}, port: {port}, type: ss}}'
            else:
                return
        elif j.startswith("trojan://"):
            server = StrText.get_str_btw(j, "@", ":", 0)
            port = StrText.get_str_btw(j, "@", "#", 0).split(":", 1)[1] if j.find("?") == -1 else StrText.get_str_btw(
                StrText.get_str_btw(j, "@", "#", 0), ":", "?", 0)
            password = StrText.get_str_btw(j, "trojan://", "@", 0).replace('<', '').replace('>', '')
            newname = f'[{datecont}]-{get_country(server)}-{str(nodecount).zfill(3)}-{server}'
            onenode = f'  - {{name: \'{newname}\', server: {server}, port: {port}, type: trojan, password: {password}'
            if '?' in j:
                tmpstr = StrText.get_str_btw(j, "?", "#", 0) + '&'
                if "sni=" in j:
                    onenode += f', sni: {StrText.get_str_btw(tmpstr, "sni=", "&", 0)}'
                onenode += ', tls: true, skip-cert-verify: true'
            onenode += '}'
        elif j.startswith("ssr://"):
            onenode = base64.b64decode(j[6:]).decode('utf-8')
            node = onenode.split('/?')[0].split(':')
            server = node[0]
            port = node[1]
            protocol = node[2]
            cipher = node[3]
            obfs = node[4]
            password = base64.b64decode(node[5]).decode('utf-8').replace('<', '').replace('>', '')
            newname = f'[{datecont}]-{get_country(server)}-{str(nodecount).zfill(3)}-{server}'
            onenode = f'  - {{name: \'{newname}\', server: {server}, port: {port}, type: ssr, cipher: {cipher}, password: {password}, protocol: {protocol}, obfs: {obfs}}}'
        else:
            return

        if not port_open(server, port):
            print(f"[ping {server}:{port}测试结果：失败]")
            return

        if server not in unique_node_names:
            if (newname.find(u'省') > -1 or newname.find(u'上海') > -1 or newname.find(
                    u'北京') > -1 or newname.find(u'重庆') > -1 or newname.find(u'内蒙') > -1):
                return
            unique_node_names.add(server)
            nodecount += 1
            clashname += f'  - \'{newname}\'\n'
            clashurl += f'{onenode}\n'
            openclashurl += f'{onenode}\n'
            clash_node_url = clash_node_url + '\n' + onenode.replace('  - {', '  - {"').replace('"', '').replace(
                '\'', '').replace(': ', '": "').replace(', ', '", "').replace('}', '"}')

            if '伊朗' not in newname and '中非' not in newname:
                telename += f'  - \'{newname}\'\n'
            proxies_url += f'{onenode}\n'
            print(f'添加第{nodecount}个节点,{newname}:{server}')
            v2ry_nodes += f'{nodestr}\n'
        else:
            print('重复已经过滤')

    except Exception as ex:
        print(f"Error processing node: {ex}")


def process_nodes(nodes):

    with concurrent.futures.ThreadPoolExecutor(max_workers=44) as executor:
        futures = [executor.submit(node_handler, j) for j in nodes]

        concurrent.futures.as_completed(futures)


def write_clash_file():
    global clashurl, openclashurl, clash_node_url, proxies_url, clashname, telename

    clashname = clashname.rstrip('\n')
    telename = telename.rstrip('\n')
    clashurl = clashurl.rstrip('\n')
    openclashurl = openclashurl.rstrip('\n')
    clash_node_url = clash_node_url.rstrip('\n')
    proxies_url = proxies_url.rstrip('\n')

    if clashname:
        with open("./res/clash-1.txt", "r", encoding='utf-8') as f:
            clash_1 = f.read()
        with open("./res/clash-2.txt", "r", encoding='utf-8') as f:
            clash_2 = f.read()
        with open("./res/clash-3.txt", "r", encoding='utf-8') as f:
            clash_3 = f.read()

        LocalFile.write_LocalFile('./o/proxies.txt', f'proxies:\n{proxies_url}')
        print('ClashNode-Proxies文件成功写入。(纯节点)')

        tmp = clash_1.replace("clash-url.txt", clashurl.replace("%", ''))
        tmp = tmp.replace("clash-name.txt", clashname)
        tmp = tmp.replace("tele-name.txt", telename)
        tmp = tmp.replace("clash-2.txt", clash_2)
        tmp = tmp.replace('\nexternal-ui: \'/usr/share/openclash/dashboard\'', '')
        LocalFile.write_LocalFile('./o/clash.yaml', tmp)
        print('Clash文件成功写入。')

        tmp = clash_1.replace("clash-url.txt", openclashurl.replace("%", ''))
        tmp = tmp.replace("clash-name.txt", clashname)
        tmp = tmp.replace("tele-name.txt", telename)
        tmp = tmp.replace("clash-2.txt", clash_2)
        LocalFile.write_LocalFile('./o/openclash.yaml', tmp)
        print('OpenClash文件成功写入。(添加UDP为True的参数)')

        tmp = clash_3.replace("clash-url.txt", clashurl.replace("%", ""))
        tmp = tmp.replace("clash-name.txt", clashname.replace('  - \'', '      - \''))
        tmp = tmp.replace("tele-name.txt", telename)
        tmp = tmp.replace("clash-2.txt", clash_3)
        tmp = tmp.replace('\nexternal-ui: \'/usr/share/openclash/dashboard\'', '')
        LocalFile.write_LocalFile('./o/clash_mate.yaml', tmp)
        print('clash_mate文件成功写入。')

        LocalFile.write_LocalFile('./o/clashnode.txt', f'proxies:{clash_node_url}')
        print('ClashNode文件成功写入。(纯节点)')


def write_v2ry_nodes():
    global v2ry_nodes
    res = base64.b64encode(v2ry_nodes.encode("utf-8"))
    LocalFile.write_LocalFile('./o/v2ry_nodes.txt', res.decode("utf-8"))


async def main(new_flag):
    global nodes_num
    if new_flag:
        sub_links = await select_sub_urls(SUB_URL_ARRAY1)
        with open('./o/allnode.txt', 'w', encoding='utf-8') as f:
            f.write(base64.b64encode('\n'.join(sub_links).encode("utf-8")).decode("utf-8"))

    with open('./o/allnode.txt', 'r') as f:
        allnodetxt = f.read()

    allnodetxt = base64.b64decode(allnodetxt).decode('utf-8')
    nodes = allnodetxt.split('\n')
    if len(nodes) <= 1:
        print("节点获取失败，不再进行")
        return
    process_nodes(nodes)
    write_clash_file()
    write_v2ry_nodes()


if __name__ == "__main__":
    new_flag = len(sys.argv) >= 2 and sys.argv[1].lower() == 'true'
    nodes_num = int(sys.argv[2]) if len(sys.argv) >= 3 else 0
    print(f'重新获取新的链接：【{new_flag}】, 数量：【{nodes_num}】')

    asyncio.run(main(new_flag))
